import * as THREE from "../../../modules/three/three.module.js";

/**
 * vector数组转换为 Float32Array
 * @param {THREE.Vector3[]} vertices
 * @return {Float32Array}
 */
export function flatten(vertices) {
  const size = vertices[0].z != undefined ? 3 : 2;
  const arr = new Float32Array(vertices.length * size);
  let count = 0;
  vertices.forEach((vector) => {
    arr[count++] = vector.x;
    arr[count++] = vector.y;
    if (size == 3) {
      arr[count++] = vector.z;
    }
  });
  return arr;
}

export function toFixedVector(vector, num) {
  if (vector instanceof THREE.Vector3) {
    const {x, y, z} = vector;
    vector.set(parseFloat(x.toFixed(num)), parseFloat(y.toFixed(num)), parseFloat(z.toFixed(num)));
  } else if (vector instanceof THREE.Vector2) {
    const {x, y} = vector;
    vector.set(parseFloat(x.toFixed(num)), parseFloat(y.toFixed(num)));
  }
  return vector;
}

function computeFontSize(str, size, family) {
  const spanDom = document.createElement('span');
  spanDom.style.fontSize = size;
  spanDom.style.opacity = '0';
  spanDom.style.fontFamily = family;
  spanDom.innerHTML = str;
  document.body.append(spanDom);
  const sizeD = {};
  sizeD.width = spanDom.offsetWidth;
  sizeD.height = spanDom.offsetHeight;
  spanDom.remove();
  return sizeD;
}

export function drawCircle() {
  const canvas = document.createElement( 'canvas' );
  canvas.width = 64;
  canvas.height = 64;
  const ctx = canvas.getContext( '2d' );
  ctx.beginPath();
  ctx.arc(32,32,20,0,2*Math.PI);
  ctx.strokeStyle = '#fff';
  ctx.lineWidth = 16;
  ctx.stroke();
  return canvas;
}

// 绘制圆圈 加 叉的关闭按钮
export function drawClose(size = 64) {
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');
  const a = 12 * Math.SQRT2;
  ctx.moveTo(a, a);
  ctx.lineTo(size - a, size - a);
  ctx.moveTo(size - a, a);
  ctx.lineTo(a, size - a);
  const lw = 4;
  ctx.lineWidth = lw
  ctx.strokeStyle = '#fff';
  ctx.stroke();
  ctx.beginPath();
  const hs = size * 0.5;
  ctx.arc(hs, hs,hs - 7 + lw * 0.5,0,2 * Math.PI);
  ctx.stroke();
  return canvas;
}

function drawPosMarker(radius, lineWidth, arrowHeight, color, txtColor, text) {
  const canvas = document.createElement( 'canvas' );
  const ctx = canvas.getContext( '2d' );
  canvas.height = (radius + lineWidth) * 4;
  canvas.width = (radius + lineWidth) * 4;
  const hw = canvas.width / 2;
  const g = radius + lineWidth/2; const h = arrowHeight;
  const vOffset = g + h;
  const f = Math.sqrt(h*h + 2*h*g);
  let e = f * g / (h + g);
  const d = Math.sqrt(g*g - e*e);
  e /= 1.5;
  const p0 = {x: hw, y: hw + g + h - vOffset};
  const p1 = {x: hw + e, y: hw + d - vOffset}; const p2 = {x: hw - e, y: hw + d - vOffset};
  ctx.beginPath();
  ctx.moveTo(p0.x, p0.y);
  ctx.lineTo(p1.x, p1.y);
  ctx.lineTo(p2.x, p2.y);
  ctx.closePath();
  ctx.fillStyle = color;
  ctx.fill();

  ctx.beginPath();
  // ctx.fillStyle = color;
  ctx.strokeStyle = color;
  ctx.lineWidth = lineWidth;
  ctx.arc(canvas.width/2, canvas.height/2 - vOffset + 5, radius, Math.PI*2, false);
  // ctx.fill();
  ctx.stroke();
  if (text) {
    const textColor = txtColor || '#d25b21';
    const size = computeFontSize(text, '50px', 'Times New Roman');
    ctx.font = '50px Times New Roman';
    ctx.fillStyle = textColor;
    ctx.fillText(text, hw - size.width/2, hw + size.height/3 - vOffset);
    // ctx.lineWidth = 1;
    // ctx.strokeStyle = "#d25b21";
    // ctx.strokeText(text, hw, hw);
  }
  return canvas;
}

export function getPositionMarker(text, color, txtColor) {
  const markDefaultColor = '#ff0000';
  color = color || markDefaultColor;
  const canvas = drawPosMarker(30, 18, 21, color, txtColor, text);
  const sprite = makeCanvasSprite(canvas);
  return sprite;
}

// 根据canvas图形制作sprite
export function makeCanvasSprite(canvas) {
  const texture = new THREE.Texture(canvas);
  texture.needsUpdate = true;
  const spriteMaterial = new THREE.SpriteMaterial({map: texture, sizeAttenuation: false});
  const sprite = new THREE.Sprite(spriteMaterial);
  sprite.center = new THREE.Vector2(0.5, 0.5);
  sprite.scale.set(canvas.width/window.innerWidth, canvas.height/window.innerWidth, 1.0);
  // sprite.material.depthWrite = false;
  sprite.material.depthTest = false;
  sprite.renderOrder = 9;
  return sprite;
}

export function bufferAttributeToVectors(attr) {
  const target = [];
  for (let i = 0; i < attr.count; i++) {
    let v;
    switch (attr.itemSize) {
      case 2:
        v = new THREE.Vector2().fromBufferAttribute(attr, i);
        break;
      case 3:
        v = new THREE.Vector3().fromBufferAttribute(attr, i);
        break;
      case 4:
        v = new THREE.Vector4().fromBufferAttribute(attr, i);
        break;
    }
    target.push(v);
  }
  return target;
}

export function vector3sToFloat32BufferAttribute(vector3s) {
  return new THREE.Float32BufferAttribute(flatten(vector3s), 3);
}

// 归一化uv坐标
export function normalizeUvVectors(uvVectors) {
  const box2 = new THREE.Box2().setFromPoints(uvVectors);
  const {min, max} = box2;
  const w = max.x - min.x;
  const h = max.y - min.y;
  const size = w > h ? w : h;
  const offset = min.clone().add(max).divideScalar(2);
  const vector2sNormalized = [];
  uvVectors.forEach((point) => {
    const u = (point.x - offset.x) / size + 0.5;
    const v = (point.y - offset.y) / size + 0.5;
    vector2sNormalized.push(u, v);
  });
  return vector2sNormalized;
}

// 根据文字生产canvas, textOption: {size: 40, textColor: '#000', outlineColor: '#fff', outline: true}
export function generateTextCanvas(text, textOption) {
  const option = textOption || {};
  const canvas = document.createElement( 'canvas' );
  const context = canvas.getContext( '2d' );
  const size = option.size || 40;
  const fontStr = 'normal 400 ' + size + 'px Microsoft YaHei';
  context.font = fontStr;
  const strLst = text.split('\n');
  let maxWdith = -Infinity;
  for (const str of strLst) {
    const measured = context.measureText(str);
    if (maxWdith < measured.width) {
      maxWdith = measured.width;
    }
  }
  canvas.width = maxWdith + 20; // 根据文字内容获取宽度
  const lineHeight = size * 1.5; // fontsize * 1.5
  canvas.height = lineHeight * strLst.length;
  // let obj = computeFontSize(text, '40px', 'Microsoft YaHei');
  // console.log("generateCanvas, ", obj);
  const offset = 1.8 * size / 40;
  const hasOutline = option.outline || true;
  strLst.forEach((str, index) => {
    context.beginPath();
    const x = 10; const y = size * (1 + index);
    context.font = fontStr;
    if (hasOutline) {
      context.fillStyle = option.outlineColor || '#fff';
      context.fillText(str, x-offset, y+offset);
      context.fillText(str, x+offset, y+offset);
      context.fillText(str, x+offset, y-offset);
      context.fillText(str, x-offset, y-offset);
    }
    context.fillStyle = option.textColor || '#000';
    context.fillText(str, x, y);
    context.fill();
  });

  // return convertToSDF(canvas);
  return canvas;
}

export function makeCanvasTextSprite(canvas) {
  const texture = new THREE.Texture(canvas);
  texture.minFilter = THREE.LinearFilter;
  texture.needsUpdate = true;
  const spriteMaterial = new THREE.SpriteMaterial({map: texture, sizeAttenuation: false});
  const sprite = new THREE.Sprite(spriteMaterial);
  sprite.center = new THREE.Vector2(0.5, 0);
  const poi = {w: canvas.width, h: canvas.height};
  sprite.scale.set(poi.w/(window.innerHeight*2.0), poi.h/(window.innerHeight*2.0), 1.0);
  sprite.material.depthWrite = false;
  sprite.material.depthTest = false;
  sprite.material.transparent = true;
  sprite.userData.poi = poi;
  sprite.renderOrder = 9;
  return sprite;
}

export function createTextSprite(txt, txtOption) {
  const canvas = generateTextCanvas(txt, txtOption);
  const sprite = makeCanvasTextSprite(canvas);
  return sprite;
}

/**
 * 获取对象的原始包围盒
 * @param {THREE.Object3D} object
 * @param {THREE.Box3} box3
 * @param {THREE.Vector3[8] | null} points0
 * @return {THREE.Vector3[8]}
 */
export function getOriginBox3Points(object, box3, points0 = null) {
  const _position = new THREE.Vector3()
  const _scale = new THREE.Vector3()
  const _rotation = new THREE.Euler()
  _position.copy(object.position)
  _rotation.copy(object.rotation)
  _scale.copy(object.scale)
  object.position.set(0, 0, 0)
  object.rotation.set(0, 0, 0)
  object.scale.set(1, 1, 1)
  const parentBak = object.parent
  object.removeFromParent()
  object.updateMatrixWorld(true)

  box3.setFromObject(object)

  object.position.copy(_position)
  object.rotation.copy(_rotation)
  object.scale.copy(_scale)
  if (parentBak) {
    parentBak.add(object)
  }
  object.updateMatrixWorld(true)
  /*
    5____4
  1/___0/|
  | 6__|_7
  2/___3/

  0: max.x, max.y, max.z
  1: min.x, max.y, max.z
  2: min.x, min.y, max.z
  3: max.x, min.y, max.z
  4: max.x, max.y, min.z
  5: min.x, max.y, min.z
  6: min.x, min.y, min.z
  7: max.x, min.y, min.z
  */
  const min = box3.min
  const max = box3.max
  let points = points0
  if (!points0 || points0.length === 0) {
    points = []
    for (let i = 0; i < 8; i++) {
      points.push(new THREE.Vector3())
    }
  }
  points[0].set(max.x, max.y, max.z)
  points[1].set(min.x, max.y, max.z)
  points[2].set(min.x, max.y, min.z)
  points[3].set(max.x, max.y, min.z)
  points[4].set(max.x, min.y, max.z)
  points[5].set(min.x, min.y, max.z)
  points[6].set(min.x, min.y, min.z)
  points[7].set(max.x, min.y, min.z)
  return points
}

// ----------------------------------------------------------
export const PI2 = Math.PI * 2;
/**
 * @return 返回值范围是[0, PI * 2)
 */
function getAngle(x, y) {
  let a = Math.atan2(y, x);
  if (a > PI2) {
    a -= PI2;
  }
  if (a < 0) {
    a += PI2;
  }
  return a;
}

/**
 * @return 返回值范围是(-PI, PI]
 */
export function angleDiff(srcAngle, dstAngle) {
  let diff = dstAngle - srcAngle;
  if (diff > Math.PI) {
    diff -= PI2;
  } else if (diff <= -Math.PI) {
    diff += PI2;
  }
  return diff;
}

/**
 * 获取 oldDir 旋转多少弧度后 可以和 newDir 重合
 * @param {{x: number, y: number}} oldDir
 * @param {{x: number, y: number}} newDir
 * @return {number} 弧度
 */
export function getOldToNewAngle(oldDir, newDir) {
  const angle1 = getAngle(newDir.x, newDir.y);
  const angle2 = getAngle(oldDir.x, oldDir.y);
  return angleDiff(angle2, angle1);
}

export function createContourLine(vector2s, z, color = '#d40099') {
  const lineMat = new THREE.LineBasicMaterial({
    color: color,
    toneMapped: false,
    transparent: true,
    depthTest: false,
    depthWrite: false,
  });
  const vector3s = vector2s.map((it) => new THREE.Vector3(it.x, it.y, z));
  if (vector2s.length > 2) {
    vector3s.push(vector3s[0]);
  }
  const lineGeo = new THREE.BufferGeometry();
  lineGeo.setAttribute('position', new THREE.BufferAttribute(flatten(vector3s), 3));
  const lineSegment = new THREE.Line(lineGeo, lineMat);
  return lineSegment;
}

export function createDashedContourLine(vector2s, color = '#d40099') {
  const lineMat = new THREE.LineDashedMaterial({
    color: color,
    toneMapped: false,
    gapSize: 0.4,
    dashSize: 0.4,
  });
  const arr = vector2s.slice();
  if (vector2s.length > 2) {
    arr.push(vector2s[0]);
  }
  const lineGeo = new THREE.BufferGeometry().setFromPoints(arr);
  const lineSegment = new THREE.Line(lineGeo, lineMat);
  return lineSegment;
}

/**
 * 已知 v0, v1, v2是钝角三角形的三个顶点，找出哪个顶点在钝角上
 * @param {THREE.Vector3} v0
 * @param {THREE.Vector3} v1
 * @param {THREE.Vector3} v2
 * @return {THREE.Vector3|null}
 */
export function findObtusePoint(v0, v1, v2) {
  if (isObtuseAngle(v0, v1, v2)) {
    return v0;
  } else if (isObtuseAngle(v1, v0, v2)) {
    return v1;
  } else if (isObtuseAngle(v2, v1, v0)) {
    return v2;
  } else {
    return null;
  }
}

/**
 * 返回 true 如何是钝角
 * @param {THREE.Vector3} p 角点
 * @param {THREE.Vector3} a 角一边的顶点
 * @param {THREE.Vector3} b 角另一边的顶点
 */
function isObtuseAngle(p, a, b) {
  const pa = a.clone().sub(p);
  const pb = b.clone().sub(p);
  return pa.dot(pb) < 0; // 负数 为钝角，0为直角，正数为锐角
}

/**
 * 重置模型及其子对象的变换，三维模型是一个树形结构的对象，每个树节点都可能带有自己的变换
 * 此方法的目的：重置三维模型中的每个树节点 position, scale, rotation三个属性
 * @param {THREE.Object3D} obj 模型对象
 */
export function resetTransform(obj) {
  const transformedGemetries = [];

  obj.updateMatrixWorld(true);
  obj.traverse(obj3d => {
    obj3d.position.set(0, 0, 0);
    obj3d.rotation.set(0, 0, 0);
    obj3d.scale.set(1, 1, 1);
    if (obj3d.geometry && !transformedGemetries.includes(obj3d.geometry)) {
      transformedGemetries.push(obj3d.geometry);
      obj3d.geometry.applyMatrix4(obj3d.matrixWorld);
      obj3d.geometry.computeBoundingBox();
      obj3d.geometry.computeBoundingSphere();
    }
  })
  obj.updateMatrixWorld(true);
}

export function box2Intersects(a, b) {
  // if (!a.intersectsBox(b)) {
  // 	return false
  // }
  const intersected = a.max.x < b.min.x || a.min.x > b.max.x ||
		a.max.y < b.min.y || a.min.y > b.max.y ? false : true;
  if (!intersected) {
    return false;
  }
  // 针对特殊情况的排除： 两个盒子正好接触
  if (a.min.x === b.max.x || b.min.x === a.max.x) {
    return false;
  }
  if (a.min.y === b.max.y || b.min.y === a.max.y) {
    return false;
  }
  // if (a.min.z === b.max.z || b.min.z === a.max.z) {
  // 	return false
  // }
  return true;
}

// https://www.jianshu.com/p/39328e0805a0
/**
 * 采用 步进法 寻找点集合 points0 的凸包
 * @param {THREE.Vector2[]} points0
 * @return {THREE.Vector2[]} 凸包轮廓
 */
export function findConvexHull(points0) {
  const map = new Map();
  points0.forEach((it) => {
    const id = it.x.toFixed(4) + '_' + it.y.toFixed(4);
    map.set(id, new THREE.Vector2(it.x, it.y));
  });
  const points = [];
  map.forEach((value, key) => {
    points.push(value);
  });
  let maxX = -Infinity;
  let index = -1;
  for (let i = 0; i < points.length; i++) {
    if (maxX < points[i].x) {
      maxX = points[i].x;
      index = i;
    }
  }
  const arr0 = points.slice();
  const arr = arr0.splice(index).concat(arr0);
  const convexHull = [arr[0]];
  const unCheckedPts = arr.slice(1);
  while (true) {
    const obj = findNextConvexPoint(convexHull, unCheckedPts);
    if (convexHull.includes(obj.point)) {
      break;
    }
    convexHull.push(obj.point);
    unCheckedPts.splice(obj.index, 1);
  }
  return convexHull;
}

/**
 * 找出凸包的下一个轮廓点
 * @param {THREE.Vector2[]} inConvexPts 已经在凸包上的顶点列表
 * @param {THREE.Vector2[]} unCheckedPts 没有在凸包上的顶点列表
 * @return {{index: number, point: THREE.Vector2}}
 */
function findNextConvexPoint(inConvexPts, unCheckedPts) {
  let maxDot = -Infinity;
  let ind = -1;
  const first = inConvexPts[0];
  const last = inConvexPts[inConvexPts.length - 1];
  let direction = new THREE.Vector2(0, 1);
  if (inConvexPts.length > 1) {
    direction = last.clone().sub(inConvexPts[inConvexPts.length - 2]).normalize();
  }
  // 特意把凸包轮廓的起始点 first 加入到 unCheckedPts，如果该函数把 first 返回了，说明凸包轮廓首尾相接了
  const arr = unCheckedPts.concat([first]);
  arr.forEach((point, index) => {
    const nextDir = point.clone().sub(last).normalize();
    const dotVal = direction.dot(nextDir);
    if (dotVal > maxDot) {
      maxDot = dotVal;
      ind = index;
    }
  });
  return {point: arr[ind], index: ind};
}

const screenDefault = {
  left: 0,
  top: 0,
  width: window.innerWidth,
  height: window.innerHeight,
};

export function getMouseRaycaster(camera, x, y, screen = screenDefault) {
  const raycaster = new THREE.Raycaster();
  if (!screen) {
    screen = screenDefault;
  }
  const screenPos = canvasCoordToScreenCoord({x, y}, screen);
  raycaster.setFromCamera(screenPos, camera);
  return raycaster;
}

/**
 * 返回obj3d 所属场景树的 树根节点
 * @param {THREE.Object3D} obj3d
 * @return {THREE.Object3D}
 */
export function findScene(obj3d) {
  let cur = obj3d
  while (cur.parent) {
    cur = cur.parent;
  }
  return cur;
}

const p0 = new THREE.Vector3();
const p1 = new THREE.Vector3();
const p2 = new THREE.Vector3();
const p3 = new THREE.Vector3();
const mat = new THREE.Matrix4();
const matRot = new THREE.Matrix4();
/**
 * 获取旋转包围盒的四个顶点坐标
 */
function getObbCorners2D(obb) {
  const hs = obb.halfSize;
  const offset = obb.center;
  p0.set(hs.x, hs.y, 0);
  p1.set(-hs.x, hs.y, 0);
  p2.set(-hs.x, -hs.y, 0);
  p3.set(hs.x, -hs.y, 0);
  mat.makeTranslation(offset.x, offset.y, offset.z);
  matRot.setFromMatrix3(obb.rotation);
  mat.multiply(matRot);
  return [p0, p1, p2, p3].map(it => it.applyMatrix4(mat));
}

/**
 * obb 完全包含 otherObb 时 返回 true
 * @param {OBB} obb 带旋转的包围盒
 * @param {OBB} otherObb 带旋转的包围盒
 * @return {boolean}
 */
export function obbContainsOther(obb, otherObb) {
  const arr = getObbCorners2D(otherObb);
  for (let i = 0; i < arr.length; i++) {
    arr[i].z = obb.center.z;
    if (!obb.containsPoint(arr[i])) {
      return false;
    }
  }
  return true;
}

/**
 * 将一个canvas坐标系中的点坐标归一化到[-1, 1]的区间，并且右和上为正方向
 * @param {{x:number, y:number}} point
 * @return {{x:number, y:number}}
 */
export function canvasCoordToScreenCoord(point, screen = screenDefault) {
  const px = point.x - screen.left;
  const py = point.y - screen.top;
  return {
    x: px / (screen.width * 1) * 2 - 1,
    y: -py / (screen.height * 1) * 2 + 1,
  };
}

const _tempPos = new THREE.Vector3();
// 3d世界坐标转为屏幕坐标
export function coord3dToScreenPos(position, camera, canvasWidth, canvasHeight) {
  _tempPos.x = position.x;
  _tempPos.y = position.y;
  _tempPos.z = position.z;
  const standardVector = _tempPos.project(camera); // 世界坐标转标准设备坐标， 标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间
  const a = canvasWidth / 2;
  const b = canvasHeight / 2;
  const x = Math.round(standardVector.x * a + a);// 标准设备坐标转屏幕坐标
  const y = Math.round(-standardVector.y * b + b);// 标准设备坐标转屏幕坐标
  return {x: x, y: y};
}

export function interpolatePoints(start, end, stepSize) {
  let distance = start.distanceTo(end)
  const alpha = stepSize / distance
  const target = [start]
  let progress = alpha
  while (progress < 1) {
    const point = start.clone().lerp(end, progress)
    progress += alpha
    target.push(point)
  }
  target.push(end)
  return target
}

export function vectorsToBufferAttribute(vertices) {
  const size = vertices[0].z !== undefined ? 3 : 2
  const arr = new Float32Array(vertices.length * size)
  let count = 0
  vertices.forEach(vector => {
    arr[count++] = vector.x
    arr[count++] = vector.y
    if (size === 3) {
      arr[count++] = vector.z
    }
  })
  return new THREE.BufferAttribute(arr, size)
}

export function uvBufferAttributeToVectors(attr) {
  const arr = []
  for (let i = 0; i < attr.count; i++) {
    const v = new THREE.Vector2().fromBufferAttribute(attr, i)
    arr.push(v)
  }
  return arr
}

export function buildCircleGeometry(radius, segments = 64) {
  const angle = Math.PI * 2.0 / segments
  const vectors = []
  let curAngle = 0
  for (let i = 0; i < segments; i++) {
    const point = new THREE.Vector3(0, 0, 0)
    point.y = radius * Math.sin(curAngle)
    point.x = radius * Math.cos(curAngle)
    vectors.push(point)
    curAngle += angle
  }
  vectors.push(vectors[0])
  const geometry = new THREE.BufferGeometry()
  geometry.setAttribute('position', vectorsToBufferAttribute(vectors))
  return geometry
}


export const removeArrayItem = (array, item) => {
  const index = array.indexOf(item);
  if (index >= 0) {
    array.splice(index, 1);
  }
}

// 集合A - 集合B，arrA 会被修改，arrB不改变
export function arrAsubB(arrA, arrB) {
  arrB.forEach(it => {
    const ind = arrA.indexOf(it)
    if (ind >= 0) {
      arrA.splice(ind, 1)
    }
  })
  return arrA
}
